﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace PrattParsing
{
    [DebuggerNonUserCode]
    public class PrattParser<E>
    {
        public const bool ASSOC_LEFT = false, ASSOC_RIGHT = true;

        private IScanner scanner;
        private List<Token> buffer;
        private Grammar<E> grammar;
        private Stack<ParseContext<E>> contexts;
        private ParseContext<E> currentContext;

        public PrattParser(IScanner scanner)
        {
            this.scanner = scanner;
            this.buffer = new List<Token>();
            this.grammar = null;
            this.contexts = new Stack<ParseContext<E>>();

        }

        protected void SetGrammar(Grammar<E> grammar)
        {
            this.grammar = grammar;
            EnterContext("base");
        }

        [DebuggerNonUserCode]
        public E parse()
        {
            return parse(0);
        }
        public E parse(int prec)
        {
            Token token = consume();
            PrefixParselet<E> prefix = getPrefix(token);
            if (prefix == null)
            {
                throw new ParseException("Could not parse " + token + " in current context.", token);
            }
            E left = prefix.parse(this, token);
            while (prec < precedence())
            {
                token = consume();
                InfixParselet<E> infix = getInfix(token);
                if (infix == null)
                {
                    throw new ParseException("Coud not parse " + token + " in current context.", token);
                }
                left = infix.parse(this, left, token);
            }
            return left;
        }

        [DebuggerNonUserCode]
        private int precedence()
        {
            InfixParselet<E> parser = getInfix(lookahead(0));
            if (parser != null) 
                return parser.precedence();
            return 0;
        }

        [DebuggerNonUserCode]
        public bool accept(TokenMatch accepted)
        {
            Token token = lookahead(0);
            if (!accepted.Matches(token))
                return false;
            consume();
            return true;
        }

        [DebuggerNonUserCode]
        public Token consume(TokenMatch expected)
        {
            Token token = lookahead(0);
            if (!expected.Matches(token))
            {
                throw new ParseException("Expected " + expected + " and found " + token + ".", token);
            }
            return consume();
        }
        [DebuggerNonUserCode]
        public Token consume()
        {
            // Make sure we've read the token.
            Token token = lookahead(0);
            buffer.RemoveAt(0);
            return token;
        }

        [DebuggerNonUserCode]
        public Token current()
        {
            return lookahead(0);
        }

        //public Token match(TokenMatch expected)
        //{
        //    Token token = lookahead(0);
        //    if (expected.Matches(token))
        //    {
        //        consume();
        //        return token;
        //    }
        //    return null;
        //}

        [DebuggerNonUserCode]
        private Token lookahead(int distance)
        {
            // Read in as many as needed.
            while (distance >= buffer.Count)
                buffer.Add(scanner.getToken());
            // Get the queued token.
            return buffer[distance];
        }

        public void EnterContext(ParseContext<E> context)
        {
            contexts.Push(currentContext = context);
        }
        public void EnterContext(string contextName)
        {
            EnterContext(grammar[contextName]);
        }
        public void ExitContext()
        {
            contexts.Pop();
            currentContext = contexts.Peek();
        }

        [DebuggerNonUserCode]
        private PrefixParselet<E> getPrefix(Token token)
        {
            return currentContext.prefixMap().get(token);
        }
        [DebuggerNonUserCode]
        private InfixParselet<E> getInfix(Token token)
        {
            return currentContext.infixMap().get(token);
        }
    }
}
